This chapter covers a number of jQuery and JavaScript best practices, in no particular order. Many of the best practices in this chapter are based on the jQuery Anti-Patterns for Performance presentation by Paul Irish.
In a for loop, don't access the length property of an array every time; cache it beforehand.
var myLength = myArray.length; for (var i = 0; i < myLength; i++) { // do stuff }
Touching the DOM comes at a cost; if you're adding a lot of elements to the DOM, do it all at once, not one at a time.
// this is bad $.each(myArray, function(i, item) { var newListItem = '<li>' + item + '</li>'; $('#ballers').append(newListItem); }); // better: do this var frag = document.createDocumentFragment(); $.each(myArray, function(i, item) { var newListItem = '<li>' + item + '</li>'; frag.appendChild(newListItem); }); $('#ballers')[0].appendChild(frag); // or do this var myHtml = ''; $.each(myArray, function(i, item) { html += '<li>' + item + '</li>'; }); $('#ballers').html(myHtml);
Don't repeat yourself; if you're repeating yourself, you're doing it wrong.
// BAD if ($eventfade.data('currently') != 'showing') { $eventfade.stop(); } if ($eventhover.data('currently') != 'showing') { $eventhover.stop(); } if ($spans.data('currently') != 'showing') { $spans.stop(); } // GOOD!! var $elems = [$eventfade, $eventhover, $spans]; $.each($elems, function(i,elem) { if (elem.data('currently') != 'showing') { elem.stop(); } });
Anonymous functions bound everywhere are a pain. They're difficult to debug, maintain, test, or reuse. Instead, use an object literal to organize and name your handlers and callbacks.
// BAD $(document).ready(function() { $('#magic').click(function(e) { $('#yayeffects').slideUp(function() { // ... }); }); $('#happiness').load(url + ' #unicorns', function() { // ... }); }); // BETTER var PI = { onReady : function() { $('#magic').click(PI.candyMtn); $('#happiness').load(PI.url + ' #unicorns', PI.unicornCb); }, candyMtn : function(e) { $('#yayeffects').slideUp(PI.slideCb); }, slideCb : function() { ... }, unicornCb : function() { ... } }; $(document).ready(PI.onReady);
Selector optimization is less important than it used to be, as more browsers implement document.querySelectorAll()
and the burden of selection shifts from jQuery to the browser. However, there are still some tips to keep in mind.
Beginning your selector with an ID is always best.
// fast $('#container div.robotarm'); // super-fast $('#container').find('div.robotarm');
The $.fn.find
approach is faster because the first selection is handled without going through the Sizzle selector engine — ID-only selections are handled using document.getElementById()
, which is extremely fast because it is native to the browser.
Be specific on the right-hand side of your selector, and less specific on the left.
// unoptimized $('div.data .gonzalez'); // optimized $('.data td.gonzalez');
Use tag.class
if possible on your right-most selector, and just tag
or just .class
on the left.
Avoid excessive specificity.
$('.data table.attendees td.gonzalez'); // better: drop the middle if possible $('.data td.gonzalez');
A "flatter" DOM also helps improve selector performance, as the selector engine has fewer layers to traverse when looking for an element.
Selections that specify or imply that a match could be found anywhere can be very slow.
$('.buttons > *'); // extremely expensive $('.buttons').children(); // much better $('.gender :radio'); // implied universal selection $('.gender *:radio'); // same thing, explicit now $('.gender input:radio'); // much better
Event delegation allows you to bind an event handler to one container element (for example, an unordered list) instead of multiple contained elements (for example, list items). jQuery makes this easy with $.fn.live and $.fn.delegate. Where possible, you should use $.fn.delegate
instead of $.fn.live
, as it eliminates the need for an unnecessary selection, and its explicit context (vs. $.fn.live
's context of document
) reduces overhead by approximately 80%.
In addition to performance benefits, event delegation also allows you to add new contained elements to your page without having to re-bind the event handlers for them as they're added.
// bad (if there are lots of list items) $('li.trigger').click(handlerFn); // better: event delegation with $.fn.live $('li.trigger').live('click', handlerFn); // best: event delegation with $.fn.delegate // allows you to specify a context easily $('#myList').delegate('li.trigger', 'click', handlerFn);
The DOM is slow; you want to avoid manipulating it as much as possible. jQuery introduced $.fn.detach
in version 1.4 to help address this issue, allowing you to remove an element from the DOM while you work with it.
var $table = $('#myTable'); var $parent = $table.parent(); $table.detach(); // ... add lots and lots of rows to table $parent.append($table);
If you're changing the CSS of more than 20 elements using $.fn.css, consider adding a style tag to the page instead for a nearly 60% increase in speed.
// fine for up to 20 elements, slow after that $('a.swedberg').css('color', '#asd123'); $('<style type="text/css">a.swedberg { color : #asd123 }</style>') .appendTo('head');
$.data
Instead of $.fn.data
Using $.data on a DOM element instead of calling $.fn.data on a jQuery selection can be up to 10 times faster. Be sure you understand the difference between a DOM element and a jQuery selection before doing this, though.
// regular $(elem).data(key,value); // 10x faster $.data(elem,key,value);
jQuery won't tell you if you're trying to run a whole lot of code on an empty selection — it will proceed as though nothing's wrong. It's up to you to verify that your selection contains some elements.
// BAD: this runs three functions // before it realizes there's nothing // in the selection $('#nosuchthing').slideUp(); // Better var $mySelection = $('#nosuchthing'); if ($mySelection.length) { $mySelection.slideUp(); } // BEST: add a doOnce plugin jQuery.fn.doOnce = function(func){ this.length && func.apply(this); return this; } $('li.cartitems').doOnce(function(){ // make it ajax! \o/ });
This guidance is especially applicable for jQuery UI widgets, which have a lot of overhead even when the selection doesn't contain elements.
Variables can be defined in one statement instead of several.
// old & busted var test = 1; var test2 = function() { ... }; var test3 = test2(test); // new hotness var test = 1, test2 = function() { ... }, test3 = test2(test);
In self-executing functions, variable definition can be skipped all together.
(function(foo, bar) { ... })(1, 2);
// old way if (type == 'foo' || type == 'bar') { ... } // better if (/^(foo|bar)$/.test(type)) { ... } // object literal lookup if (({ foo : 1, bar : 1 })[type]) { ... }
Use the source as your documentation — bookmark http://bit.ly/jqsource and refer to it often.